﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq.Expressions;
using System.Reflection;

using static iTool.ClusterComponent.DashboardTelemetryConsumer;

namespace iTool.Cloud.Database
{
    public static class PropertyUtils<TReader>
        where TReader : class, new()
    {
        static Dictionary<string, Type> PropertyTypeDic;
        static Dictionary<string, Action<TReader, object>> SetPropertyDic;
        static Dictionary<string, Func<TReader, object>> GetPropertyDic;
        static PropertyUtils()
        {
            PropertyTypeDic = new Dictionary<string, Type>();
            SetPropertyDic = new Dictionary<string, Action<TReader, object>>();
            GetPropertyDic = new Dictionary<string, Func<TReader, object>>();
            var propertyInfos = typeof(TReader).GetProperties();
            foreach (PropertyInfo property in propertyInfos)
            {
                SetPropertyDic.Add(property.Name.ToUpper(), CreateSetPropertyValueAction(property));
                GetPropertyDic.Add(property.Name.ToUpper(), CreateGetPropertyValueFunc(property));
                PropertyTypeDic.Add(property.Name.ToUpper(), property.PropertyType);
            }
        }

        public static TOut GetValue<TOut>(TReader reader, string propertyName)
        {
            try
            {
                if (GetPropertyDic.TryGetValue(propertyName.ToUpper(), out Func<TReader, object> action))
                {
                    object val = action.Invoke(reader);
                    if (val is TOut result)
                    {
                        return result;
                    }
                    return (TOut)Convert.ChangeType(val, typeof(TOut));
                }
            }
            catch
            {
#pragma warning disable CS8603 // 可能返回 null 引用。
                return default;
#pragma warning restore CS8603 // 可能返回 null 引用。
            }
#pragma warning disable CS8603 // 可能返回 null 引用。
            return default;
#pragma warning restore CS8603 // 可能返回 null 引用。
        }

        public static void SetValue(TReader reader, string propertyName, object value)
        {
            var key = propertyName.ToUpper();

            if (SetPropertyDic.TryGetValue(key, out Action <TReader, object> action))
            {
                var type = PropertyTypeDic[key];

                if (value.GetType() != type)
                    value = Convert.ChangeType(value, PropertyTypeDic[key]);

                action.Invoke(reader, value);
            }
        }

        static Func<TReader, object> CreateGetPropertyValueFunc(PropertyInfo property)
        {
            // 定义参数
            var target = Expression.Parameter(typeof(TReader));
            // 定义转换
            //var castTarget = Expression.Convert(target, typeof(TReader));
            // 获取参数值
            var getPropertyValue = Expression.Property(target, property);
            // 返回值转换
            var castPropertyvalue = Expression.Convert(getPropertyValue, typeof(object));
            return Expression.Lambda<Func<TReader, object>>(castPropertyvalue, target).Compile();
        }

        static Action<TReader, object> CreateSetPropertyValueAction(PropertyInfo property)
        {
            var target = Expression.Parameter(typeof(TReader));
            var propertyValue = Expression.Parameter(typeof(object));
            //var castTarget = Expression.Convert(target, typeof(IFoo));
            var castPropertyValue = Expression.Convert(propertyValue, property.PropertyType);
            var setPropertyValue = Expression.Call(target, property.GetSetMethod(), castPropertyValue);
            return Expression.Lambda<Action<TReader, object>>(setPropertyValue, target, propertyValue).Compile();
        }

        public static List<TReader> ConvertDataReaderToEntity(DbDataReader reader)
        {
            try
            {
                var result = new List<TReader>(reader?.FieldCount ?? 0);

                if (reader == null)
                    return result;

                while (reader.Read())
                {
                    TReader entity = new TReader();
                    for (int i = 0; i < reader.FieldCount; i++)
                    {
                        SetValue(entity, reader.GetName(i), reader[i]);
                    }
                    result.Add(entity);
                }
                return result;
            }
            catch (Exception ex)
            {
                throw new Exception("转换出错!", ex);
            }
        }
    }
}
